/*
 * Copyright (c) 2012, 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package java.lang.reflect;

import java.lang.annotation.*;
import java.util.Map;
import java.util.Objects;
import sun.reflect.annotation.AnnotationParser;
import sun.reflect.annotation.AnnotationSupport;
import sun.reflect.annotation.TypeAnnotationParser;
import sun.reflect.annotation.TypeAnnotation;
import sun.reflect.generics.repository.ConstructorRepository;

/**
 * A shared superclass for the common functionality of {@link Method}
 * and {@link Constructor}.
 *
 * @since 1.8
 */
public abstract class Executable extends AccessibleObject
    implements Member, GenericDeclaration {

  /*
   * Only grant package-visibility to the constructor.
   */
  Executable() {
  }

  /**
   * Accessor method to allow code sharing
   */
  abstract byte[] getAnnotationBytes();

  /**
   * Accessor method to allow code sharing
   */
  abstract Executable getRoot();

  /**
   * Does the Executable have generic information.
   */
  abstract boolean hasGenericInformation();

  abstract ConstructorRepository getGenericInfo();

  boolean equalParamTypes(Class<?>[] params1, Class<?>[] params2) {
        /* Avoid unnecessary cloning */
    if (params1.length == params2.length) {
      for (int i = 0; i < params1.length; i++) {
        if (params1[i] != params2[i]) {
          return false;
        }
      }
      return true;
    }
    return false;
  }

  Annotation[][] parseParameterAnnotations(byte[] parameterAnnotations) {
    return AnnotationParser.parseParameterAnnotations(
        parameterAnnotations,
        sun.misc.SharedSecrets.getJavaLangAccess().
            getConstantPool(getDeclaringClass()),
        getDeclaringClass());
  }

  void separateWithCommas(Class<?>[] types, StringBuilder sb) {
    for (int j = 0; j < types.length; j++) {
      sb.append(types[j].getTypeName());
      if (j < (types.length - 1)) {
        sb.append(",");
      }
    }

  }

  void printModifiersIfNonzero(StringBuilder sb, int mask, boolean isDefault) {
    int mod = getModifiers() & mask;

    if (mod != 0 && !isDefault) {
      sb.append(Modifier.toString(mod)).append(' ');
    } else {
      int access_mod = mod & Modifier.ACCESS_MODIFIERS;
      if (access_mod != 0) {
        sb.append(Modifier.toString(access_mod)).append(' ');
      }
      if (isDefault) {
        sb.append("default ");
      }
      mod = (mod & ~Modifier.ACCESS_MODIFIERS);
      if (mod != 0) {
        sb.append(Modifier.toString(mod)).append(' ');
      }
    }
  }

  String sharedToString(int modifierMask,
      boolean isDefault,
      Class<?>[] parameterTypes,
      Class<?>[] exceptionTypes) {
    try {
      StringBuilder sb = new StringBuilder();

      printModifiersIfNonzero(sb, modifierMask, isDefault);
      specificToStringHeader(sb);

      sb.append('(');
      separateWithCommas(parameterTypes, sb);
      sb.append(')');
      if (exceptionTypes.length > 0) {
        sb.append(" throws ");
        separateWithCommas(exceptionTypes, sb);
      }
      return sb.toString();
    } catch (Exception e) {
      return "<" + e + ">";
    }
  }

  /**
   * Generate toString header information specific to a method or
   * constructor.
   */
  abstract void specificToStringHeader(StringBuilder sb);

  String sharedToGenericString(int modifierMask, boolean isDefault) {
    try {
      StringBuilder sb = new StringBuilder();

      printModifiersIfNonzero(sb, modifierMask, isDefault);

      TypeVariable<?>[] typeparms = getTypeParameters();
      if (typeparms.length > 0) {
        boolean first = true;
        sb.append('<');
        for (TypeVariable<?> typeparm : typeparms) {
          if (!first) {
            sb.append(',');
          }
          // Class objects can't occur here; no need to test
          // and call Class.getName().
          sb.append(typeparm.toString());
          first = false;
        }
        sb.append("> ");
      }

      specificToGenericStringHeader(sb);

      sb.append('(');
      Type[] params = getGenericParameterTypes();
      for (int j = 0; j < params.length; j++) {
        String param = params[j].getTypeName();
        if (isVarArgs() && (j == params.length - 1)) // replace T[] with T...
        {
          param = param.replaceFirst("\\[\\]$", "...");
        }
        sb.append(param);
        if (j < (params.length - 1)) {
          sb.append(',');
        }
      }
      sb.append(')');
      Type[] exceptions = getGenericExceptionTypes();
      if (exceptions.length > 0) {
        sb.append(" throws ");
        for (int k = 0; k < exceptions.length; k++) {
          sb.append((exceptions[k] instanceof Class) ?
              ((Class) exceptions[k]).getName() :
              exceptions[k].toString());
          if (k < (exceptions.length - 1)) {
            sb.append(',');
          }
        }
      }
      return sb.toString();
    } catch (Exception e) {
      return "<" + e + ">";
    }
  }

  /**
   * Generate toGenericString header information specific to a
   * method or constructor.
   */
  abstract void specificToGenericStringHeader(StringBuilder sb);

  /**
   * Returns the {@code Class} object representing the class or interface
   * that declares the executable represented by this object.
   */
  public abstract Class<?> getDeclaringClass();

  /**
   * Returns the name of the executable represented by this object.
   */
  public abstract String getName();

  /**
   * Returns the Java language {@linkplain Modifier modifiers} for
   * the executable represented by this object.
   */
  public abstract int getModifiers();

  /**
   * Returns an array of {@code TypeVariable} objects that represent the
   * type variables declared by the generic declaration represented by this
   * {@code GenericDeclaration} object, in declaration order.  Returns an
   * array of length 0 if the underlying generic declaration declares no type
   * variables.
   *
   * @return an array of {@code TypeVariable} objects that represent the type variables declared by
   * this generic declaration
   * @throws GenericSignatureFormatError if the generic signature of this generic declaration does
   * not conform to the format specified in <cite>The Java&trade; Virtual Machine
   * Specification</cite>
   */
  public abstract TypeVariable<?>[] getTypeParameters();

  /**
   * Returns an array of {@code Class} objects that represent the formal
   * parameter types, in declaration order, of the executable
   * represented by this object.  Returns an array of length
   * 0 if the underlying executable takes no parameters.
   *
   * @return the parameter types for the executable this object represents
   */
  public abstract Class<?>[] getParameterTypes();

  /**
   * Returns the number of formal parameters (whether explicitly
   * declared or implicitly declared or neither) for the executable
   * represented by this object.
   *
   * @return The number of formal parameters for the executable this object represents
   * @since 1.8
   */
  public int getParameterCount() {
    throw new AbstractMethodError();
  }

  /**
   * Returns an array of {@code Type} objects that represent the formal
   * parameter types, in declaration order, of the executable represented by
   * this object. Returns an array of length 0 if the
   * underlying executable takes no parameters.
   *
   * <p>If a formal parameter type is a parameterized type,
   * the {@code Type} object returned for it must accurately reflect
   * the actual type parameters used in the source code.
   *
   * <p>If a formal parameter type is a type variable or a parameterized
   * type, it is created. Otherwise, it is resolved.
   *
   * @return an array of {@code Type}s that represent the formal parameter types of the underlying
   * executable, in declaration order
   * @throws GenericSignatureFormatError if the generic method signature does not conform to the
   * format specified in <cite>The Java&trade; Virtual Machine Specification</cite>
   * @throws TypeNotPresentException if any of the parameter types of the underlying executable
   * refers to a non-existent type declaration
   * @throws MalformedParameterizedTypeException if any of the underlying executable's parameter
   * types refer to a parameterized type that cannot be instantiated for any reason
   */
  public Type[] getGenericParameterTypes() {
    if (hasGenericInformation()) {
      return getGenericInfo().getParameterTypes();
    } else {
      return getParameterTypes();
    }
  }

  /**
   * Behaves like {@code getGenericParameterTypes}, but returns type
   * information for all parameters, including synthetic parameters.
   */
  Type[] getAllGenericParameterTypes() {
    final boolean genericInfo = hasGenericInformation();

    // Easy case: we don't have generic parameter information.  In
    // this case, we just return the result of
    // getParameterTypes().
    if (!genericInfo) {
      return getParameterTypes();
    } else {
      final boolean realParamData = hasRealParameterData();
      final Type[] genericParamTypes = getGenericParameterTypes();
      final Type[] nonGenericParamTypes = getParameterTypes();
      final Type[] out = new Type[nonGenericParamTypes.length];
      final Parameter[] params = getParameters();
      int fromidx = 0;
      // If we have real parameter data, then we use the
      // synthetic and mandate flags to our advantage.
      if (realParamData) {
        for (int i = 0; i < out.length; i++) {
          final Parameter param = params[i];
          if (param.isSynthetic() || param.isImplicit()) {
            // If we hit a synthetic or mandated parameter,
            // use the non generic parameter info.
            out[i] = nonGenericParamTypes[i];
          } else {
            // Otherwise, use the generic parameter info.
            out[i] = genericParamTypes[fromidx];
            fromidx++;
          }
        }
      } else {
        // Otherwise, use the non-generic parameter data.
        // Without method parameter reflection data, we have
        // no way to figure out which parameters are
        // synthetic/mandated, thus, no way to match up the
        // indexes.
        return genericParamTypes.length == nonGenericParamTypes.length ?
            genericParamTypes : nonGenericParamTypes;
      }
      return out;
    }
  }

  /**
   * Returns an array of {@code Parameter} objects that represent
   * all the parameters to the underlying executable represented by
   * this object.  Returns an array of length 0 if the executable
   * has no parameters.
   *
   * <p>The parameters of the underlying executable do not necessarily
   * have unique names, or names that are legal identifiers in the
   * Java programming language (JLS 3.8).
   *
   * @return an array of {@code Parameter} objects representing all the parameters to the executable
   * this object represents.
   * @throws MalformedParametersException if the class file contains a MethodParameters attribute
   * that is improperly formatted.
   * @since 1.8
   */
  public Parameter[] getParameters() {
    // TODO: This may eventually need to be guarded by security
    // mechanisms similar to those in Field, Method, etc.
    //
    // Need to copy the cached array to prevent users from messing
    // with it.  Since parameters are immutable, we can
    // shallow-copy.
    return privateGetParameters().clone();
  }

  private Parameter[] synthesizeAllParams() {
    final int realparams = getParameterCount();
    final Parameter[] out = new Parameter[realparams];
    for (int i = 0; i < realparams; i++)
    // TODO: is there a way to synthetically derive the
    // modifiers?  Probably not in the general case, since
    // we'd have no way of knowing about them, but there
    // may be specific cases.
    {
      out[i] = new Parameter("arg" + i, 0, this, i);
    }
    return out;
  }

  private void verifyParameters(final Parameter[] parameters) {
    final int mask = Modifier.FINAL | Modifier.SYNTHETIC | Modifier.MANDATED;

    if (getParameterTypes().length != parameters.length) {
      throw new MalformedParametersException(
          "Wrong number of parameters in MethodParameters attribute");
    }

    for (Parameter parameter : parameters) {
      final String name = parameter.getRealName();
      final int mods = parameter.getModifiers();

      if (name != null) {
        if (name.isEmpty() || name.indexOf('.') != -1 ||
            name.indexOf(';') != -1 || name.indexOf('[') != -1 ||
            name.indexOf('/') != -1) {
          throw new MalformedParametersException("Invalid parameter name \"" + name + "\"");
        }
      }

      if (mods != (mods & mask)) {
        throw new MalformedParametersException("Invalid parameter modifiers");
      }
    }
  }

  private Parameter[] privateGetParameters() {
    // Use tmp to avoid multiple writes to a volatile.
    Parameter[] tmp = parameters;

    if (tmp == null) {

      // Otherwise, go to the JVM to get them
      try {
        tmp = getParameters0();
      } catch (IllegalArgumentException e) {
        // Rethrow ClassFormatErrors
        throw new MalformedParametersException("Invalid constant pool index");
      }

      // If we get back nothing, then synthesize parameters
      if (tmp == null) {
        hasRealParameterData = false;
        tmp = synthesizeAllParams();
      } else {
        hasRealParameterData = true;
        verifyParameters(tmp);
      }

      parameters = tmp;
    }

    return tmp;
  }

  boolean hasRealParameterData() {
    // If this somehow gets called before parameters gets
    // initialized, force it into existence.
    if (parameters == null) {
      privateGetParameters();
    }
    return hasRealParameterData;
  }

  private transient volatile boolean hasRealParameterData;
  private transient volatile Parameter[] parameters;

  private native Parameter[] getParameters0();

  native byte[] getTypeAnnotationBytes0();

  // Needed by reflectaccess
  byte[] getTypeAnnotationBytes() {
    return getTypeAnnotationBytes0();
  }

  /**
   * Returns an array of {@code Class} objects that represent the
   * types of exceptions declared to be thrown by the underlying
   * executable represented by this object.  Returns an array of
   * length 0 if the executable declares no exceptions in its {@code
   * throws} clause.
   *
   * @return the exception types declared as being thrown by the executable this object represents
   */
  public abstract Class<?>[] getExceptionTypes();

  /**
   * Returns an array of {@code Type} objects that represent the
   * exceptions declared to be thrown by this executable object.
   * Returns an array of length 0 if the underlying executable declares
   * no exceptions in its {@code throws} clause.
   *
   * <p>If an exception type is a type variable or a parameterized
   * type, it is created. Otherwise, it is resolved.
   *
   * @return an array of Types that represent the exception types thrown by the underlying
   * executable
   * @throws GenericSignatureFormatError if the generic method signature does not conform to the
   * format specified in <cite>The Java&trade; Virtual Machine Specification</cite>
   * @throws TypeNotPresentException if the underlying executable's {@code throws} clause refers to
   * a non-existent type declaration
   * @throws MalformedParameterizedTypeException if the underlying executable's {@code throws}
   * clause refers to a parameterized type that cannot be instantiated for any reason
   */
  public Type[] getGenericExceptionTypes() {
    Type[] result;
    if (hasGenericInformation() &&
        ((result = getGenericInfo().getExceptionTypes()).length > 0)) {
      return result;
    } else {
      return getExceptionTypes();
    }
  }

  /**
   * Returns a string describing this {@code Executable}, including
   * any type parameters.
   *
   * @return a string describing this {@code Executable}, including any type parameters
   */
  public abstract String toGenericString();

  /**
   * Returns {@code true} if this executable was declared to take a
   * variable number of arguments; returns {@code false} otherwise.
   *
   * @return {@code true} if an only if this executable was declared to take a variable number of
   * arguments.
   */
  public boolean isVarArgs() {
    return (getModifiers() & Modifier.VARARGS) != 0;
  }

  /**
   * Returns {@code true} if this executable is a synthetic
   * construct; returns {@code false} otherwise.
   *
   * @return true if and only if this executable is a synthetic construct as defined by <cite>The
   * Java&trade; Language Specification</cite>.
   * @jls 13.1 The Form of a Binary
   */
  public boolean isSynthetic() {
    return Modifier.isSynthetic(getModifiers());
  }

  /**
   * Returns an array of arrays of {@code Annotation}s that
   * represent the annotations on the formal parameters, in
   * declaration order, of the {@code Executable} represented by
   * this object.  Synthetic and mandated parameters (see
   * explanation below), such as the outer "this" parameter to an
   * inner class constructor will be represented in the returned
   * array.  If the executable has no parameters (meaning no formal,
   * no synthetic, and no mandated parameters), a zero-length array
   * will be returned.  If the {@code Executable} has one or more
   * parameters, a nested array of length zero is returned for each
   * parameter with no annotations. The annotation objects contained
   * in the returned arrays are serializable.  The caller of this
   * method is free to modify the returned arrays; it will have no
   * effect on the arrays returned to other callers.
   *
   * A compiler may add extra parameters that are implicitly
   * declared in source ("mandated"), as well as parameters that
   * are neither implicitly nor explicitly declared in source
   * ("synthetic") to the parameter list for a method.  See {@link
   * java.lang.reflect.Parameter} for more information.
   *
   * @return an array of arrays that represent the annotations on the formal and implicit
   * parameters, in declaration order, of the executable represented by this object
   * @see java.lang.reflect.Parameter
   * @see java.lang.reflect.Parameter#getAnnotations
   */
  public abstract Annotation[][] getParameterAnnotations();

  Annotation[][] sharedGetParameterAnnotations(Class<?>[] parameterTypes,
      byte[] parameterAnnotations) {
    int numParameters = parameterTypes.length;
    if (parameterAnnotations == null) {
      return new Annotation[numParameters][0];
    }

    Annotation[][] result = parseParameterAnnotations(parameterAnnotations);

    if (result.length != numParameters) {
      handleParameterNumberMismatch(result.length, numParameters);
    }
    return result;
  }

  abstract void handleParameterNumberMismatch(int resultLength, int numParameters);

  /**
   * {@inheritDoc}
   *
   * @throws NullPointerException {@inheritDoc}
   */
  public <T extends Annotation> T getAnnotation(Class<T> annotationClass) {
    Objects.requireNonNull(annotationClass);
    return annotationClass.cast(declaredAnnotations().get(annotationClass));
  }

  /**
   * {@inheritDoc}
   *
   * @throws NullPointerException {@inheritDoc}
   * @since 1.8
   */
  @Override
  public <T extends Annotation> T[] getAnnotationsByType(Class<T> annotationClass) {
    Objects.requireNonNull(annotationClass);

    return AnnotationSupport
        .getDirectlyAndIndirectlyPresent(declaredAnnotations(), annotationClass);
  }

  /**
   * {@inheritDoc}
   */
  public Annotation[] getDeclaredAnnotations() {
    return AnnotationParser.toArray(declaredAnnotations());
  }

  private transient Map<Class<? extends Annotation>, Annotation> declaredAnnotations;

  private synchronized Map<Class<? extends Annotation>, Annotation> declaredAnnotations() {
    if (declaredAnnotations == null) {
      Executable root = getRoot();
      if (root != null) {
        declaredAnnotations = root.declaredAnnotations();
      } else {
        declaredAnnotations = AnnotationParser.parseAnnotations(
            getAnnotationBytes(),
            sun.misc.SharedSecrets.getJavaLangAccess().
                getConstantPool(getDeclaringClass()),
            getDeclaringClass());
      }
    }
    return declaredAnnotations;
  }

  /**
   * Returns an {@code AnnotatedType} object that represents the use of a type to
   * specify the return type of the method/constructor represented by this
   * Executable.
   *
   * If this {@code Executable} object represents a constructor, the {@code
   * AnnotatedType} object represents the type of the constructed object.
   *
   * If this {@code Executable} object represents a method, the {@code
   * AnnotatedType} object represents the use of a type to specify the return
   * type of the method.
   *
   * @return an object representing the return type of the method or constructor represented by this
   * {@code Executable}
   * @since 1.8
   */
  public abstract AnnotatedType getAnnotatedReturnType();

  /* Helper for subclasses of Executable.
   *
   * Returns an AnnotatedType object that represents the use of a type to
   * specify the return type of the method/constructor represented by this
   * Executable.
   *
   * @since 1.8
   */
  AnnotatedType getAnnotatedReturnType0(Type returnType) {
    return TypeAnnotationParser.buildAnnotatedType(getTypeAnnotationBytes0(),
        sun.misc.SharedSecrets.getJavaLangAccess().
            getConstantPool(getDeclaringClass()),
        this,
        getDeclaringClass(),
        returnType,
        TypeAnnotation.TypeAnnotationTarget.METHOD_RETURN);
  }

  /**
   * Returns an {@code AnnotatedType} object that represents the use of a
   * type to specify the receiver type of the method/constructor represented
   * by this Executable object. The receiver type of a method/constructor is
   * available only if the method/constructor has a <em>receiver
   * parameter</em> (JLS 8.4.1).
   *
   * If this {@code Executable} object represents a constructor or instance
   * method that does not have a receiver parameter, or has a receiver
   * parameter with no annotations on its type, then the return value is an
   * {@code AnnotatedType} object representing an element with no
   * annotations.
   *
   * If this {@code Executable} object represents a static method, then the
   * return value is null.
   *
   * @return an object representing the receiver type of the method or constructor represented by
   * this {@code Executable}
   * @since 1.8
   */
  public AnnotatedType getAnnotatedReceiverType() {
    if (Modifier.isStatic(this.getModifiers())) {
      return null;
    }
    return TypeAnnotationParser.buildAnnotatedType(getTypeAnnotationBytes0(),
        sun.misc.SharedSecrets.getJavaLangAccess().
            getConstantPool(getDeclaringClass()),
        this,
        getDeclaringClass(),
        getDeclaringClass(),
        TypeAnnotation.TypeAnnotationTarget.METHOD_RECEIVER);
  }

  /**
   * Returns an array of {@code AnnotatedType} objects that represent the use
   * of types to specify formal parameter types of the method/constructor
   * represented by this Executable. The order of the objects in the array
   * corresponds to the order of the formal parameter types in the
   * declaration of the method/constructor.
   *
   * Returns an array of length 0 if the method/constructor declares no
   * parameters.
   *
   * @return an array of objects representing the types of the formal parameters of the method or
   * constructor represented by this {@code Executable}
   * @since 1.8
   */
  public AnnotatedType[] getAnnotatedParameterTypes() {
    return TypeAnnotationParser.buildAnnotatedTypes(getTypeAnnotationBytes0(),
        sun.misc.SharedSecrets.getJavaLangAccess().
            getConstantPool(getDeclaringClass()),
        this,
        getDeclaringClass(),
        getAllGenericParameterTypes(),
        TypeAnnotation.TypeAnnotationTarget.METHOD_FORMAL_PARAMETER);
  }

  /**
   * Returns an array of {@code AnnotatedType} objects that represent the use
   * of types to specify the declared exceptions of the method/constructor
   * represented by this Executable. The order of the objects in the array
   * corresponds to the order of the exception types in the declaration of
   * the method/constructor.
   *
   * Returns an array of length 0 if the method/constructor declares no
   * exceptions.
   *
   * @return an array of objects representing the declared exceptions of the method or constructor
   * represented by this {@code Executable}
   * @since 1.8
   */
  public AnnotatedType[] getAnnotatedExceptionTypes() {
    return TypeAnnotationParser.buildAnnotatedTypes(getTypeAnnotationBytes0(),
        sun.misc.SharedSecrets.getJavaLangAccess().
            getConstantPool(getDeclaringClass()),
        this,
        getDeclaringClass(),
        getGenericExceptionTypes(),
        TypeAnnotation.TypeAnnotationTarget.THROWS);
  }

}
